/* Shared base styles for index pages (root, protected, private) and docs
   pages (site-map, site-stats, lifespan-atlas plan). Loaded BEFORE the
   page-specific stylesheet so page rules can override.

   Workflow.css layers on top of this (workflow.html loads both): it keeps its
   own GitHub-style palette but now expresses light/dark via the same
   light-dark() + color-scheme mechanism, so the manual theme toggle reaches it
   too. */

/* ---- Theme tokens ----
   Each colour is declared exactly once via light-dark(light, dark); the value
   that wins follows the `color-scheme` property (single source of truth — no
   duplicated palette across a media query).

   Default `dark light` = AUTO: follow the OS, falling back to dark when the OS
   states no preference (preserves the site's long-standing dark default). The
   Settings toggle overrides this by setting a data-theme attribute on <html>,
   which narrows color-scheme to one keyword — applied before first paint by
   page-widgets/theme-init.js, so navigation never flashes the wrong theme. */
:root {
  color-scheme: dark light;                    /* auto (OS-driven, dark default) */
  /* THE COLUMN (user decision 2026-10-21: 800px -> 880px, one value for
     every site, docs and Hub page): `.container`'s width, and anything
     that measures the gutter beside it, reads this. */
  --column: 880px;
  /* THE PAGE'S EDGE, named (2026-10-22): `body`'s padding below, and
     anything that paints the gap — the Hub's bedtime frame — reads these
     rather than restating the rule. */
  --edge-y: 2rem;
  --edge-x: clamp(1rem, (100vw - var(--column)) / 2, 2rem);
  /* THE ROW (user decision 2026-10-17): every one-line control or entry —
     a link row, a search box, a dropdown, a caption — is exactly this tall,
     and rows stack with this gap, so two columns side by side stay on one
     grid. The height is the link row's own: 1.1rem text at line-height 1.4
     plus 0.35rem padding above and below. */
  --row-h: calc(1.1rem * 1.4 + 0.7rem);
  --row-gap: 0.2rem;
  /* THE BREADCRUMB ROW's one height (user decisions 2026-10-18): the trail's
     text size at line-height 1.5, and everything that sits on that line —
     the crumbs, the Coordination Hub's part switch, the page controls at
     the line's right end — is exactly this tall or fits inside it. Fixed
     lengths, not em, so no element on the line can resolve it differently.
     (0.85em × 1.5 of the page text until today, the same size here.) */
  --crumb-font: 0.85rem;
  --crumb-h: calc(var(--crumb-font) * 1.5);
  --bg:      light-dark(#f5f5f5, #0f0f0f);
  --surface: light-dark(#ffffff, #1a1a1a);
  --border:  light-dark(#d0d0d0, #2e2e2e);
  --accent:  light-dark(#3a5fd9, #7c9ef5);
  --text:    light-dark(#1a1a1a, #e0e0e0);
  --muted:   light-dark(#666,    #888);
  /* The FUTURE GROUND — "this time is not settled". Used by the Site Log
     rings, where the website's current version band dissolves into it past
     today and it then carries on to the rim (the shared ring-log engine's two
     future registers). Distinct from --muted on purpose: muted is a
     TYPOGRAPHY grey and the gap it fills means "no data here", which happens
     in the past too. Pairs with the lifespan-atlas theme's `futureGrey`, the
     SSOT for the same two registers on the app side — keep the two in step. */
  --future-grey: light-dark(#d3d3d3, #4a4a4a);
  /* THE YOU-ARE-HERE RED — "now is here". The site-stats chart's today
     guide (stats.css .today-line). One value in both themes, as the atlas
     has it: it is a marker on top of whatever ground, not a ground. Pairs
     with the lifespan-atlas's ANCHOR_MARKER_COLOR (lib/eventManifoldSpec),
     the anchor / today marker red on every atlas surface — keep the two in
     step, as --future-grey is kept with the theme's futureGrey. */
  --anchor-red: #e53935;
}
:root[data-theme="light"] { color-scheme: light; }  /* forced light */
:root[data-theme="dark"]  { color-scheme: dark;  }   /* forced dark  */

/* ---- Reset + base ---- */
* { box-sizing: border-box; margin: 0; padding: 0; }

/* Reserve the scrollbar's strip so short pages (no scrollbar) and long
   pages (with scrollbar) use the same content width — no horizontal jump
   when navigating between them. ON BOTH EDGES (user decision 2026-10-21,
   moved here from hub.css): one strip each side keeps the column centred,
   so every page — website, docs, Hub — puts it in exactly the same place
   at every width. Floating scrollbars (phones, tablets) take no strip. */
html { scrollbar-gutter: stable both-edges; }

body {
  background: var(--bg);
  color: var(--text);
  /* Stack adopted from chinese-languages: native Apple system font on macOS/iOS,
     Segoe UI on Windows, system-ui as a generic fallback, then sans-serif. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  line-height: 1.7;
  /* THE PAGE'S EDGE (user decisions 2026-10-21). Top and bottom: 2rem.
     Sides: 2rem, shrinking towards 1rem only as far as keeps the full
     --column, so a narrow window loses padding before it loses column.
     The scrollbar strips sit OUTSIDE this padding, on desktop only —
     where scrollbars float (tablet, phone) there is no strip. Putting the
     strip inside the 2rem was tried and dropped the same day: Chrome
     leaves reserved strips out of 100vw, so CSS cannot measure one
     (user chose this over a script that would). */
  padding: var(--edge-y) var(--edge-x);
}

/* ---- Layout ---- */
/* The column (--column above) keeps prose/index lines near the
   readability sweet spot (50–75 characters at default font size). Apps that benefit from more
   horizontal space (lifespan-atlas, life-clock) override this. */
.container {
  max-width: var(--column);
  margin: 0 auto;
  position: relative;
  user-select: text;
  -webkit-user-select: text;
}

/* (`.back-link`, a link pinned to the container's top-right corner, lived
   here until 2026-10-19 — no page used it any more. The way up from a page
   now sits at the right end of the trail line: `.crumb-line` /
   `.up-link` below.) */

/* Breadcrumb nav above the h1. Tells visitors WHERE they are within the
   site by spelling out the path from Home to the current page. Replaces
   the back-link on shell pages (apps stay plain, with their own back-link).
   Armored against index.css's block-style global `a` rule — explicit resets
   so cascade can't make breadcrumb links into block-shaped buttons. */
.breadcrumb {
  color: var(--muted);
  font-size: var(--crumb-font);
  margin: 0 0 0.6rem;
  display: block;
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
  line-height: var(--crumb-h);
}
.breadcrumb a {
  color: var(--accent);
  text-decoration: none;
  display: inline;
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
  line-height: inherit;
  transition: none;
}
.breadcrumb a:hover { background: none; text-decoration: underline; }
.breadcrumb [aria-current="page"] { color: var(--text); }
.breadcrumb .sep { margin: 0 0.3em; opacity: 0.5; }
/* THE PART SWITCH — the 统筹中心 Coordination Hub's two parts as one
   two-way toggle in the trail's last place (user decision 2026-10-18), the
   same control on both pages so neither reads as the other's parent. The
   current part is the filled half and is not a link.
   EXACTLY THE ROW'S HEIGHT, border included (user report, same day: the
   1px border sat on top of the line box and made the row 2px taller): the
   box is --crumb-h tall and top-aligned, so it fills the line box and never
   grows it; its halves take the height left inside the border. */
.breadcrumb .part-switch {
  display: inline-flex; vertical-align: top; box-sizing: border-box;
  height: var(--crumb-h);
  border: 1px solid var(--border); border-radius: 999px; overflow: hidden;
}
/* Every part the same size — the open one, a link to another, and a part
   that cannot be opened from here (a <span>: the Noticeboard on another
   device turns the this-machine-only parts' links into text, 2026-10-20). */
.breadcrumb .part-switch > * {
  padding: 0 0.7em; line-height: calc(var(--crumb-h) - 2px);
}
.breadcrumb .part-switch > a { color: var(--muted); }
.breadcrumb .part-switch > span:not([aria-current]) {
  color: var(--muted); opacity: 0.55;
}
.breadcrumb .part-switch > a:hover {
  color: var(--accent); text-decoration: none;
}
.breadcrumb .part-switch > [aria-current="page"] {
  background: var(--accent); color: var(--bg);
}
/* THE BREADCRUMB LINE with the page's controls at its right end (index
   pages: Filter ▸ and the Planned / topic switches — user decision
   2026-10-17). The crumb keeps its own look; the line takes its spacing.
   Wraps on a narrow screen, the controls staying right-aligned. */
.crumb-line {
  display: flex; flex-wrap: wrap; align-items: center;
  column-gap: 0.9rem; row-gap: 0.2rem; margin: 0 0 0.6rem;
  /* ONE HEIGHT on every surface (user report 2026-10-18: Filter ▸ and the
     Planned switch made the line taller than a bare breadcrumb). The line
     is the breadcrumb's own line box (--crumb-h) whatever sits beside
     it; the controls below give back their vertical padding as negative
     margin, so they keep their tap area but no longer push the line. */
  min-height: var(--crumb-h);
}
.crumb-line > .breadcrumb { margin: 0; }
.head-controls { margin-left: auto; display: inline-flex; align-items: center; gap: 0.9rem; }
/* EVERYTHING ON THE TRAIL LINE TAKES THE TRAIL'S LINE BOX (user report
   2026-10-19: the part stats pages' `Workshop Stats 📊` link inherited the
   page's taller line-height and pushed their h1 down 2.7px against every
   other page). Set on the line, not on each item, so whatever sits there —
   a link, a control, a future one — measures --crumb-h like the crumbs. */
.crumb-line, .crumb-line .head-controls { line-height: var(--crumb-h); }
.head-controls > button { margin-block: -0.15rem; }
/* THE WAY UP from a page, in the same slot (user decision 2026-10-19): the
   five part stats pages' `Workshop Stats 📊` (source_stats.page,
   `workshop_link`). Trail-sized and accent like the crumbs it shares the
   line with, so it reads as navigation rather than a control. */
.head-controls > a.up-link {
  color: var(--accent); text-decoration: none; font-size: var(--crumb-font);
  display: inline; padding: 0; background: none;
}
.head-controls > a.up-link:hover { text-decoration: underline; }

/* THE CONTROLS ROW (user decisions 2026-10-24: "put [the edit button] as a
   button in the first row below the status line … so the status line is
   purely the status line", then "this should be SSOT of a plan page"): one
   row of buttons right under a page's status line — a plan page's Edit and
   Save to file, the Termination Planner's Edit, Add and lock.
   Here, not in docs.css, because the planner's part pages load only this
   sheet. The buttons share one size; each keeps its own colours (Save to
   file stays the filled one). */
/* ITS HEIGHT IS RESERVED (user decision 2026-10-25: "reserve space for
   the controls line in advance so UI doesn't move"): the lock's buttons
   are drawn by script, so the row used to open at 0px and grow to a
   button's height a moment later, pushing the page down 36px. It is one
   button tall from the start — the buttons' own height, from the same
   numbers (font size × line height + padding + border). */
.controls-row { --control-h: calc(0.9rem * 1.5 + 0.6rem + 2px);
  display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
  min-height: var(--control-h); margin: 0.3rem 0 0.9rem; }
.controls-row [hidden] { display: none !important; }
.controls-row > button, .controls-row .lock-controls > button,
.controls-row #edit-btn, .controls-row #save-btn {
  font: inherit; font-size: 0.9rem; line-height: 1.5;
  padding: 0.3rem 0.9rem; box-sizing: border-box;
  min-height: var(--control-h);
  border-radius: 4px; cursor: pointer; margin: 0; }
.controls-row > button.button, .controls-row .lock-controls > button,
.controls-row #edit-btn {
  color: var(--text); background: var(--surface);
  border: 1px solid var(--border); }
.controls-row #edit-btn.active { border-color: var(--accent);
  color: var(--accent); }
.controls-row .lock-controls { display: inline-flex; gap: 0.5rem; }
.controls-row .lock-controls:empty { display: none; }

/* ---- Head-region typography ---- */
h1 {
  font-size: 1.6rem;
  color: var(--text);
  margin-bottom: 1.2rem;
}

/* Category glyphs injected into a content page's own h1 at build time from
   site-structure.yaml's `categories:` (same emoji SSOT as the tier-index
   chips and the category-matrix column heads). Sit INLINE right after the
   title rather than floated to the container edge (user decision
   2026-08-17): at the column's width a short title leaves the far right so distant that
   the glyphs read as a stray corner widget instead of an attribute of the
   title. Inline also follows a wrapped title's last line and stays beside
   the title on phones. Emoji-only with a title/aria-label tooltip, matching
   the matrix column-head convention; slightly muted so they don't compete
   with the title text. */
.page-cats {
  display: inline-block;
  font-size: 0.75em;
  letter-spacing: 0.12em;
  margin-left: 0.5em;
  opacity: 0.85;
  vertical-align: 0.1em;
}
/* Each glyph links to the page's own tier category-matrix (unfiltered — user
   decision 2026-08-17), so the taxonomy is traversable: page → the grid →
   whatever you want to narrow to once you're looking at it. Styling
   is neutralised the same way .head-meta a is, because index-style sheets
   turn a bare `a` into a block-level button; the hover scale is the only
   affordance an emoji can really carry. Pages on a tier with no matrix keep
   an inert <span>, which is why the cursor differs by element. */
a.page-cat {
  display: inline-block; color: inherit; text-decoration: none;
  font-size: inherit; line-height: inherit; padding: 0; border-radius: 0;
  background: none; cursor: pointer; transition: transform 0.12s ease;
}
a.page-cat:hover, a.page-cat:focus-visible {
  background: none; text-decoration: none; transform: scale(1.18);
}
span.page-cat { cursor: help; }

/* Stats / metadata line directly under the h1. Used for build info,
   tier label, line counts, page-progress, etc. The negative top margin
   pulls it up close to the h1 so they read as one heading unit. */
.head-meta {
  color: var(--muted);
  font-size: 0.85em;
  margin: -0.5rem 0 1rem;
  /* ONE LINE, EVEN EMPTY (user decision 2026-10-18): a head line filled in
     after load — the docs pages' status line, the Noticeboard's `today`
     readouts — keeps its room while it waits, or when its source never
     answers, so nothing below it jumps. An index page with nothing to say
     gets the empty line too (`index_page` always writes it, 2026-10-19), so
     every page's content starts at the same height. `1lh` is this line's
     own height, whatever the font. */
  min-height: 1lh;
}
/* Inline links inside .head-meta — explicit overrides because index pages'
   global `a` rule turns links into block-style buttons (display:block,
   padding, etc.); we have to neutralize all of that for inline meta links.
   These overrides are no-ops on docs pages (where `a` is already inline) so
   they're safe to live in shared base. */
.head-meta a {
  color: inherit;
  text-decoration: none;
  display: inline;
  font-size: inherit;
  line-height: inherit;
  padding: 0;
  border-radius: 0;
  background: none;
  transition: none;
}
.head-meta a:hover { background: none; text-decoration: underline; }

/* ---- Page-level search filter ---- */
/* Sits directly under .head-meta on pages that have a list to filter
   (index pages, site-map). Hides matched-out rows via `.search-hidden`. */
.filter {
  display: block;
  width: 100%;
  height: var(--row-h);                        /* THE ROW — see :root */
  padding: 0 0.8rem;
  margin-bottom: 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-family: inherit;
  font-size: 1rem;
}
.filter:focus { outline: none; border-color: var(--accent); }
.filter::placeholder { color: var(--muted); }
.search-hidden { display: none !important; }

/* ---- Tables (shared shell; pages override padding / colors) ---- */
table { width: 100%; border-collapse: collapse; }

/* ---- Inline utility: muted secondary text ---- */
/* Same grey as .head-meta — used inline for "this is a footnote / cited
   form / secondary annotation" within otherwise-primary text. */
.muted { color: var(--muted); }

/* ---- Buttons ---- */
/* Site-wide primary/secondary action buttons. Works on both <button>
   and <a> (the latter needs text-decoration: none, set explicitly).
   Page-specific layout (margins, alignment) stays at the call site —
   only visual primitives live here.
   - .btn-primary: accent-fill (the dominant action; e.g. dissertation
     "Read PDF", map-explorer "Render" / "Save PNG").
   - .btn-secondary: accent-outline on transparent (the lower-weight
     companion; e.g. dissertation "Download LaTeX source").
   - :disabled state used by the map exporter while a save runs. */
.btn-primary,
.btn-secondary {
  display: inline-block;
  padding: 0.6rem 1.2rem;
  border-radius: 6px;
  border: 1px solid var(--accent);
  font: inherit;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: opacity 0.15s, transform 0.12s;
}
.btn-primary { background: var(--accent); color: var(--bg); }
.btn-secondary { background: transparent; color: var(--accent); }
.btn-primary:hover,
.btn-secondary:hover { opacity: 0.88; transform: translateY(-1px); }
.btn-primary:disabled,
.btn-secondary:disabled {
  opacity: 0.55;
  cursor: wait;
  transform: none;
}

/* ---- Bio entry page (site root) ---- */
/* Bio block — paragraph stack with modest vertical breath, mathematical-set
   notation and emoji-prefixed lines. Rendered as a 2-column CSS Grid so
   that all .bio-hint cells align at the same x position regardless of
   how long the substantive content (.bio-content) on each line happens
   to be. `max-content` on the first column makes the grid auto-fit to the
   widest line; `display: contents` on each <p> hoists the inner spans
   into the grid so each row is exactly [content, hint]. Lines without a
   hint still emit an empty .bio-hint to preserve column alignment. */
.bio {
  margin: 1.5rem 0 2rem;
  display: grid;
  grid-template-columns: max-content auto;
  column-gap: 1.5em;
  row-gap: 0.4rem;
  align-items: baseline;
  font-size: 1rem;
  line-height: 1.7;
  color: var(--text);
}
.bio p { display: contents; }
.bio-hint {
  color: var(--muted);
  font-size: 0.85em;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Linktree-style buttons under the bio. Same aesthetic family as a.app-card
   on index pages but slimmed: no icon, no description, label-only, full-width.
   Grid with `grid-auto-rows: 1fr` so every button stretches to the tallest
   row's height — keeps Enter (two lines: label + "best on a laptop") and
   Instagram (one line) vertically aligned regardless of content. */
.entry-links { display: grid; grid-auto-rows: 1fr; gap: 0.6rem; margin-top: 1.5rem; }
.entry-links a {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0.7rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 1.05rem;
  line-height: 1.4;
  text-decoration: none;
  text-align: center;
  transition: border-color 0.15s, background 0.15s, transform 0.12s;
}
.entry-links a:hover {
  border-color: var(--accent);
  text-decoration: none;
  transform: translateY(-1px);
}
/* Primary entry link — light-blue fill marks the intended path into the
   site, distinguishing it from secondary social links rendered as surface
   cards. Fixed light-blue (rather than var(--accent)) keeps the button
   the same gentle shade in both themes; dark text in both modes preserves
   legibility on the light background. */
.entry-links a.primary {
  background: #a8c5ff;
  border-color: #a8c5ff;
  color: #1a1a1a;
  font-weight: 500;
}
.entry-links a.primary:hover {
  background: #bdd3ff;
  border-color: #bdd3ff;
}
.entry-hint {
  font-size: 0.75em;
  font-weight: normal;
  color: var(--muted);
}
.entry-links a.primary .entry-hint { color: #1a1a1a; opacity: 0.65; }

/* ---- Appendix sections ---- */
/* h2.appendix (emitted by render_external_links_html for the {{EXTERNAL_
   LINKS}} link sections — reference material, not works; user decision
   2026-08-15): lettered A. B. … instead of numbered. The .appendix::before
   rule out-specifies the pages' generic `.container > h2::before` numeric
   prefix, so it wins without !important; pages should still scope their
   numeric counter-increment to `h2:not(.appendix)` so the numbers don't
   silently skip values. page-nav.js letters the matching Contents rows. */
.container { counter-reset: appendix-section; }
.container > h2.appendix { counter-increment: appendix-section; }
.container > h2.appendix::before { content: counter(appendix-section, upper-alpha) ". "; }

/* ---- Table of contents ---- */
/* Used by site-map, lifespan-atlas plan, workflow. Boxed surface card with a
   nested ordered list; folders render as <details>/<summary> for collapse. */
.toc { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 1.2rem 1.5rem; margin-bottom: 2.5rem; }
.toc-desc { color: var(--muted); }
/* Explicitly reset every property a page-level `h2` rule might set
   (workflow.css's body h2 has border-bottom, padding-bottom, monospace,
   etc.) so they can't leak into the TOC heading via the cascade. */
.toc h2 {
  font-size: 0.85rem;
  font-family: inherit;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  border-bottom: none;
  padding-bottom: 0;
  margin: 0 0 0.8rem;
}
.toc-header { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.toc-header h2 { margin: 0 0 0.8rem; }
.toc-controls { display: flex; align-items: baseline; font-size: 0.8rem; color: var(--muted); }
.toc-toggle-btn { background: transparent; border: 0; padding: 0; color: var(--muted); cursor: pointer; font: inherit; font-size: 0.8rem; }
.toc-toggle-btn:hover { color: var(--accent); text-decoration: underline; }
.toc ol { padding-left: 0.6rem; list-style: none; text-indent: 0; }
.toc li { margin: 0.3rem 0; }
.toc > ol > li > ol > li                               { padding-left: 3ch;   text-indent: -3ch; }
.toc > ol > li > ol > li > ol                          { margin-left: -3ch; }
.toc > ol > li > ol > li > ol > li                     { padding-left: 4.5ch; text-indent: -4.5ch; }
.toc > ol > li > ol > li > ol > li > ol                { margin-left: -4.5ch; }
.toc > ol > li > ol > li > ol > li > ol > li           { padding-left: 6ch;   text-indent: -6ch; }
/* Inline link styling armored against page-CSS leaks (e.g. index.css's
   button-style global `a` rule). Every property a global `a` might set
   is explicitly redeclared here so cascade can't fall through. */
.toc a {
  color: var(--accent);
  text-decoration: none;
  display: inline;
  font-size: inherit;
  line-height: inherit;
  padding: 0;
  border-radius: 0;
  background: none;
  transition: none;
}
.toc a:hover { background: none; text-decoration: underline; }
.toc details { margin: 0.2rem 0; }
/* Same armor pattern as .toc h2: redeclare every property a page-CSS
   `details > summary` rule (e.g. workflow.css) might set, so the cascade
   can't reach in. */
.toc summary {
  cursor: pointer;
  list-style: none;
  display: list-item;
  font-weight: inherit;
  font-size: inherit;
  color: inherit;
  border-bottom: none;
  padding: 0;
  margin: 0;
  gap: 0;
}
.toc summary::-webkit-details-marker { display: none; }
.toc summary::before { content: "\25B8 "; color: var(--muted); font-size: 0.8em; }
.toc details[open] > summary::before { content: "\25BE "; }
/* Leaf entries (no <details> wrapper) reserve the same space as the triangle
   so leaves and folders align at the same indent. */
.toc li > a::before { content: "\25B8 "; color: transparent; font-size: 0.8em; }
.toc details > ol { margin-top: 0.3rem; }
